-module(opcua_space_backend_test).

-include_lib("eunit/include/eunit.hrl").

-include("opcua.hrl").
-include("opcua_internal.hrl").

-import(opcua_space_backend, [init/0, init/1]).
-import(opcua_space_backend, [terminate/1]).
-import(opcua_space_backend, [add_nodes/2]).
-import(opcua_space_backend, [del_nodes/2]).
-import(opcua_space_backend, [add_references/2]).
-import(opcua_space_backend, [del_references/2]).
-import(opcua_space_backend, [node/2]).
-import(opcua_space_backend, [references/2]).
-import(opcua_space_backend, [references/3]).


%%% MACROS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-define(NODE(N, V), #opcua_node{
    node_id = ?NNID(N),
    node_class = #opcua_variable{value = V}
}).

-define(REF(TYPE, SOURCE, TARGET), #opcua_reference{
    type_id = ?NNID(TYPE),
    source_id = ?NNID(SOURCE),
    target_id = ?NNID(TARGET)
}).


%%% TESTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 

generated_reference_test() ->
    Spaces = [B, A] = [init(), init()],
    add_nodes(A, [?NODE(1, foo), ?NODE(2, bar)]),
    add_references(A, [?REF(100, 1, 2)]),
    add_nodes(Spaces, [?NODE(1, toto), ?NODE(3, tata)]),
    add_references(Spaces, [?REF(100, 1, 3)]),

    ?assertMatch(?NODE(1, toto), node(Spaces, 1)),
    ?assertMatch(?NODE(2, bar), node(Spaces, 2)),
    ?assertMatch(?NODE(3, tata), node(Spaces, 3)),
    ?assertMatch([?REF(100, 1, 2), ?REF(100, 1, 3)],
                 lists:sort(references(Spaces, 1, #{}))),

    terminate(B),
    terminate(A),
    ok.

adding_nodes_test() ->
    init(a),
    init(b),
    init(c),

    add_nodes(a, [?NODE(1, foo), ?NODE(2, bar), ?NODE(3, buz)]),
    add_nodes(b, [?NODE(1, pim), ?NODE(4, pam), ?NODE(5, poum)]),
    add_nodes(c, [?NODE(1, toto), ?NODE(2, tata), ?NODE(4, titi), ?NODE(6, tutu)]),

    ?assertMatch(?NODE(1, foo), node(a, 1)),
    ?assertMatch(?NODE(2, bar), node(a, 2)),
    ?assertMatch(?NODE(3, buz), node(a, 3)),
    ?assertMatch(undefined, node(a, 4)),
    ?assertMatch(undefined, node(a, 5)),
    ?assertMatch(undefined, node(a, 6)),

    ?assertMatch(?NODE(1, pim), node(b, 1)),
    ?assertMatch(undefined, node(b, 2)),
    ?assertMatch(undefined, node(b, 3)),
    ?assertMatch(?NODE(4, pam), node(b, 4)),
    ?assertMatch(?NODE(5, poum), node(b, 5)),
    ?assertMatch(undefined, node(b, 6)),

    ?assertMatch(?NODE(1, toto), node(c, 1)),
    ?assertMatch(?NODE(2, tata), node(c, 2)),
    ?assertMatch(undefined, node(c, 3)),
    ?assertMatch(?NODE(4, titi), node(c, 4)),
    ?assertMatch(undefined, node(c, 5)),
    ?assertMatch(?NODE(6, tutu), node(c, 6)),

    ?assertMatch(?NODE(1, pim), node([b, a], 1)),
    ?assertMatch(?NODE(2, bar), node([b, a], 2)),
    ?assertMatch(?NODE(3, buz), node([b, a], 3)),
    ?assertMatch(?NODE(4, pam), node([b, a], 4)),
    ?assertMatch(?NODE(5, poum), node([b, a], 5)),
    ?assertMatch(undefined, node([b, a], 6)),

    ?assertMatch(?NODE(1, foo), node([a, b], 1)),
    ?assertMatch(?NODE(2, bar), node([a, b], 2)),
    ?assertMatch(?NODE(3, buz), node([a, b], 3)),
    ?assertMatch(?NODE(4, pam), node([a, b], 4)),
    ?assertMatch(?NODE(5, poum), node([a, b], 5)),
    ?assertMatch(undefined, node([a, b], 6)),

    ?assertMatch(?NODE(1, toto), node([c, b, a], 1)),
    ?assertMatch(?NODE(2, tata), node([c, b, a], 2)),
    ?assertMatch(?NODE(3, buz), node([c, b, a], 3)),
    ?assertMatch(?NODE(4, titi), node([c, b, a], 4)),
    ?assertMatch(?NODE(5, poum), node([c, b, a], 5)),
    ?assertMatch(?NODE(6, tutu), node([c, b, a], 6)),

    terminate(c),
    terminate(b),
    terminate(a),
    ok.

deleting_nodes_test() ->
    init(a),
    init(b),
    init(c),

    add_nodes(a, [?NODE(1, foo), ?NODE(2, bar), ?NODE(3, buz)]),
    add_nodes(b, [?NODE(1, pim), ?NODE(4, pam), ?NODE(5, poum)]),
    add_nodes(c, [?NODE(1, toto), ?NODE(2, tata), ?NODE(4, titi), ?NODE(6, tutu)]),

    del_nodes(a, [?NNID(1)]),

    ?assertMatch(undefined, node(a, 1)),
    ?assertMatch(?NODE(2, bar), node(a, 2)),
    ?assertMatch(?NODE(3, buz), node(a, 3)),
    ?assertMatch(undefined, node(a, 4)),
    ?assertMatch(undefined, node(a, 5)),
    ?assertMatch(undefined, node(a, 6)),

    del_nodes(b, [?NNID(1), ?NNID(2)]),

    ?assertMatch(undefined, node([b, a], 1)),
    ?assertMatch(undefined, node([b, a], 2)),
    ?assertMatch(?NODE(3, buz), node([b, a], 3)),
    ?assertMatch(?NODE(4, pam), node([b, a], 4)),
    ?assertMatch(?NODE(5, poum), node([b, a], 5)),
    ?assertMatch(undefined, node([b, a], 6)),

    del_nodes(c, [?NNID(3), ?NNID(4)]),

    ?assertMatch(?NODE(1, toto), node([c, b, a], 1)),
    ?assertMatch(?NODE(2, tata), node([c, b, a], 2)),
    ?assertMatch(undefined, node([c, b, a], 3)),
    ?assertMatch(undefined, node([c, b, a], 4)),
    ?assertMatch(?NODE(5, poum), node([c, b, a], 5)),
    ?assertMatch(?NODE(6, tutu), node([c, b, a], 6)),

    terminate(c),
    terminate(b),
    terminate(a),
    ok.

adding_references_test() ->
    init(a),
    init(b),
    init(c),

    add_references(a, [?REF(100, 1, 3), ?REF(100, 2, 3)]),
    add_references(b, [?REF(100, 1, 4), ?REF(100, 2, 5), ?REF(100, 4, 3)]),
    add_references(c, [?REF(100, 1, 6), ?REF(100, 6, 5), ?REF(100, 3, 4)]),

    ?assertMatch([?REF(100, 1, 3)],
                 lists:sort(references(a, 1, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references(a, 1, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3)],
                 lists:sort(references(a, 1, #{direction => both}))),
    ?assertMatch([?REF(100, 2, 3)],
                 lists:sort(references(a, 2, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references(a, 2, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 3)],
                 lists:sort(references(a, 2, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references(a, 3, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3)],
                 lists:sort(references(a, 3, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3)],
                 lists:sort(references(a, 3, #{direction => both}))),
    ?assertMatch([], references(a, 4, #{})),
    ?assertMatch([], references(a, 5, #{})),
    ?assertMatch([], references(a, 6, #{})),

    ?assertMatch([?REF(100, 1, 4)],
                 lists:sort(references(b, 1, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references(b, 1, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 4)],
                 lists:sort(references(b, 1, #{direction => both}))),
    ?assertMatch([?REF(100, 2, 5)],
                 lists:sort(references(b, 2, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references(b, 2, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 5)],
                 lists:sort(references(b, 2, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references(b, 3, #{direction => forward}))),
    ?assertMatch([?REF(100, 4, 3)],
                 lists:sort(references(b, 3, #{direction => inverse}))),
    ?assertMatch([?REF(100, 4, 3)],
                 lists:sort(references(b, 3, #{direction => both}))),
    ?assertMatch([?REF(100, 4, 3)],
                 lists:sort(references(b, 4, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 4)],
                 lists:sort(references(b, 4, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 4), ?REF(100, 4, 3)],
                 lists:sort(references(b, 4, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references(b, 5, #{direction => forward}))),
    ?assertMatch([?REF(100, 2, 5)],
                 lists:sort(references(b, 5, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 5)],
                 lists:sort(references(b, 5, #{direction => both}))),
    ?assertMatch([], references(b, 6, #{})),

    ?assertMatch([?REF(100, 1, 6)],
                 lists:sort(references(c, 1, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references(c, 1, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 6)],
                 lists:sort(references(c, 1, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references(c, 2, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references(c, 2, #{direction => inverse}))),
    ?assertMatch([],
                 lists:sort(references(c, 2, #{direction => both}))),
    ?assertMatch([?REF(100, 3, 4)],
                 lists:sort(references(c, 3, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references(c, 3, #{direction => inverse}))),
    ?assertMatch([?REF(100, 3, 4)],
                 lists:sort(references(c, 3, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references(c, 4, #{direction => forward}))),
    ?assertMatch([?REF(100, 3, 4)],
                 lists:sort(references(c, 4, #{direction => inverse}))),
    ?assertMatch([?REF(100, 3, 4)],
                 lists:sort(references(c, 4, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references(c, 5, #{direction => forward}))),
    ?assertMatch([?REF(100, 6, 5)],
                 lists:sort(references(c, 5, #{direction => inverse}))),
    ?assertMatch([?REF(100, 6, 5)],
                 lists:sort(references(c, 5, #{direction => both}))),
    ?assertMatch([?REF(100, 6, 5)],
                 lists:sort(references(c, 6, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 6)],
                 lists:sort(references(c, 6, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 6), ?REF(100, 6, 5)],
                 lists:sort(references(c, 6, #{direction => both}))),

    ?assertMatch([?REF(100, 1, 3), ?REF(100, 1, 4)],
                 lists:sort(references([b, a], 1, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 1, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 1, 4)],
                 lists:sort(references([b, a], 1, #{direction => both}))),
    ?assertMatch([?REF(100, 2, 3), ?REF(100, 2, 5)],
                 lists:sort(references([b, a], 2, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 2, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 3), ?REF(100, 2, 5)],
                 lists:sort(references([b, a], 2, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 3, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3), ?REF(100, 4, 3)],
                 lists:sort(references([b, a], 3, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3), ?REF(100, 4, 3)],
                 lists:sort(references([b, a], 3, #{direction => both}))),
    ?assertMatch([?REF(100, 4, 3)],
                 lists:sort(references([b, a], 4, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 4)],
                 lists:sort(references([b, a], 4, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 4), ?REF(100, 4, 3)],
                 lists:sort(references([b, a], 4, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 5, #{direction => forward}))),
    ?assertMatch([?REF(100, 2, 5)],
                 lists:sort(references([b, a], 5, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 5)],
                 lists:sort(references([b, a], 5, #{direction => both}))),
    ?assertMatch([], references(b, 6, #{})),

    ?assertMatch([?REF(100, 1, 3), ?REF(100, 1, 4)],
                 lists:sort(references([a, b], 1, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([a, b], 1, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 1, 4)],
                 lists:sort(references([a, b], 1, #{direction => both}))),
    ?assertMatch([?REF(100, 2, 3), ?REF(100, 2, 5)],
                 lists:sort(references([a, b], 2, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([a, b], 2, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 3), ?REF(100, 2, 5)],
                 lists:sort(references([a, b], 2, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references([a, b], 3, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3), ?REF(100, 4, 3)],
                 lists:sort(references([a, b], 3, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3), ?REF(100, 4, 3)],
                 lists:sort(references([a, b], 3, #{direction => both}))),
    ?assertMatch([?REF(100, 4, 3)],
                 lists:sort(references([a, b], 4, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 4)],
                 lists:sort(references([a, b], 4, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 4), ?REF(100, 4, 3)],
                 lists:sort(references([a, b], 4, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references([a, b], 5, #{direction => forward}))),
    ?assertMatch([?REF(100, 2, 5)],
                 lists:sort(references([a, b], 5, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 5)],
                 lists:sort(references([a, b], 5, #{direction => both}))),
    ?assertMatch([], references(b, 6, #{})),

    ?assertMatch([?REF(100, 1, 3), ?REF(100, 1, 4), ?REF(100, 1, 6)],
                 lists:sort(references([c, b, a], 1, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([c, b, a], 1, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 1, 4), ?REF(100, 1, 6)],
                 lists:sort(references([c, b, a], 1, #{direction => both}))),
    ?assertMatch([?REF(100, 2, 3), ?REF(100, 2, 5)],
                 lists:sort(references([c, b, a], 2, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([c, b, a], 2, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 3), ?REF(100, 2, 5)],
                 lists:sort(references([c, b, a], 2, #{direction => both}))),
    ?assertMatch([?REF(100, 3, 4)],
                 lists:sort(references([c, b, a], 3, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3), ?REF(100, 4, 3)],
                 lists:sort(references([c, b, a], 3, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3), ?REF(100, 3, 4), ?REF(100, 4, 3)],
                 lists:sort(references([c, b, a], 3, #{direction => both}))),
    ?assertMatch([?REF(100, 4, 3)],
                 lists:sort(references([c, b, a], 4, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 4), ?REF(100, 3, 4)],
                 lists:sort(references([c, b, a], 4, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 4), ?REF(100, 3, 4), ?REF(100, 4, 3)],
                 lists:sort(references([c, b, a], 4, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references([c, b, a], 5, #{direction => forward}))),
    ?assertMatch([?REF(100, 2, 5), ?REF(100, 6, 5)],
                 lists:sort(references([c, b, a], 5, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 5), ?REF(100, 6, 5)],
                 lists:sort(references([c, b, a], 5, #{direction => both}))),
    ?assertMatch([?REF(100, 6, 5)],
                 lists:sort(references([c, b, a], 6, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 6)],
                 lists:sort(references([c, b, a], 6, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 6), ?REF(100, 6, 5)],
                 lists:sort(references([c, b, a], 6, #{direction => both}))),

    terminate(c),
    terminate(b),
    terminate(a),
    ok.

deleting_references_test() ->
    init(a),
    init(b),
    init(c),

    add_references(a, [?REF(100, 1, 3), ?REF(100, 2, 3), ?REF(100, 4, 1),
                       ?REF(100, 4, 6), ?REF(100, 5, 6)]),
    add_references(b, [?REF(100, 1, 4), ?REF(100, 2, 5), ?REF(100, 4, 3),
                       ?REF(100, 5, 6)]),
    add_references(c, [?REF(100, 1, 6), ?REF(100, 3, 4), ?REF(100, 6, 5),
                       ?REF(100, 5, 6)]),

    del_references(a, [?REF(100, 4, 6)]),

    ?assertMatch([?REF(100, 1, 3)],
                 lists:sort(references(a, 1, #{direction => forward}))),
    ?assertMatch([?REF(100, 4, 1)],
                 lists:sort(references(a, 1, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 4, 1)],
                 lists:sort(references(a, 1, #{direction => both}))),
    ?assertMatch([?REF(100, 2, 3)],
                 lists:sort(references(a, 2, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references(a, 2, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 3)],
                 lists:sort(references(a, 2, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references(a, 3, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3)],
                 lists:sort(references(a, 3, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3)],
                 lists:sort(references(a, 3, #{direction => both}))),
    ?assertMatch([?REF(100, 4, 1)],
                 lists:sort(references(a, 4, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references(a, 4, #{direction => inverse}))),
    ?assertMatch([?REF(100, 4, 1)],
                 lists:sort(references(a, 4, #{direction => both}))),
    ?assertMatch([?REF(100, 5, 6)],
                 lists:sort(references(a, 5, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references(a, 5, #{direction => inverse}))),
    ?assertMatch([?REF(100, 5, 6)],
                 lists:sort(references(a, 5, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references(a, 6, #{direction => forward}))),
    ?assertMatch([?REF(100, 5, 6)],
                 lists:sort(references(a, 6, #{direction => inverse}))),
    ?assertMatch([?REF(100, 5, 6)],
                 lists:sort(references(a, 6, #{direction => both}))),

    del_references(b, [?REF(100, 1, 4), ?REF(100, 5, 6)]),

    ?assertMatch([?REF(100, 1, 3)],
                 lists:sort(references([b, a], 1, #{direction => forward}))),
    ?assertMatch([?REF(100, 4, 1)],
                 lists:sort(references([b, a], 1, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 4, 1)],
                 lists:sort(references([b, a], 1, #{direction => both}))),
    ?assertMatch([?REF(100, 2, 3), ?REF(100, 2, 5)],
                 lists:sort(references([b, a], 2, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 2, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 3), ?REF(100, 2, 5)],
                 lists:sort(references([b, a], 2, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 3, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3), ?REF(100, 4, 3)],
                 lists:sort(references([b, a], 3, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3), ?REF(100, 4, 3)],
                 lists:sort(references([b, a], 3, #{direction => both}))),
    ?assertMatch([?REF(100, 4, 1), ?REF(100, 4, 3)],
                 lists:sort(references([b, a], 4, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 4, #{direction => inverse}))),
    ?assertMatch([?REF(100, 4, 1), ?REF(100, 4, 3)],
                 lists:sort(references([b, a], 4, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 5, #{direction => forward}))),
    ?assertMatch([?REF(100, 2, 5)],
                 lists:sort(references([b, a], 5, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 5)],
                 lists:sort(references([b, a], 5, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 6, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 6, #{direction => inverse}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 6, #{direction => both}))),

    del_references(c, [?REF(100, 2, 5), ?REF(100, 3, 4), ?REF(100, 4, 1)]),

    ?assertMatch([?REF(100, 1, 3), ?REF(100, 1, 6)],
                 lists:sort(references([c, b, a], 1, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([c, b, a], 1, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 1, 6)],
                 lists:sort(references([c, b, a], 1, #{direction => both}))),
    ?assertMatch([?REF(100, 2, 3)],
                 lists:sort(references([c, b, a], 2, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([c, b, a], 2, #{direction => inverse}))),
    ?assertMatch([?REF(100, 2, 3)],
                 lists:sort(references([c, b, a], 2, #{direction => both}))),
    ?assertMatch([],
                 lists:sort(references([c, b, a], 3, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3), ?REF(100, 4, 3)],
                 lists:sort(references([c, b, a], 3, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(100, 2, 3), ?REF(100, 4, 3)],
                 lists:sort(references([c, b, a], 3, #{direction => both}))),
    ?assertMatch([?REF(100, 4, 3)],
                 lists:sort(references([c, b, a], 4, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references([c, b, a], 4, #{direction => inverse}))),
    ?assertMatch([?REF(100, 4, 3)],
                 lists:sort(references([c, b, a], 4, #{direction => both}))),
    ?assertMatch([?REF(100, 5, 6)],
                 lists:sort(references([c, b, a], 5, #{direction => forward}))),
    ?assertMatch([?REF(100, 6, 5)],
                 lists:sort(references([c, b, a], 5, #{direction => inverse}))),
    ?assertMatch([?REF(100, 5, 6), ?REF(100, 6, 5)],
                 lists:sort(references([c, b, a], 5, #{direction => both}))),
    ?assertMatch([?REF(100, 6, 5)],
                 lists:sort(references([c, b, a], 6, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 6), ?REF(100, 5, 6)],
                 lists:sort(references([c, b, a], 6, #{direction => inverse}))),
    ?assertMatch([?REF(100, 1, 6), ?REF(100, 5, 6), ?REF(100, 6, 5)],
                 lists:sort(references([c, b, a], 6, #{direction => both}))),

    terminate(c),
    terminate(b),
    terminate(a),
    ok.


reference_subtypes_test() ->
    init(a),
    init(b),
    init(c),

    % The reference type hierarchy used in this test:
    %
    %               10011
    %      / 1001 <
    %     /         10012
    % 100
    %     \         10021
    %      \ 1002 <
    %               10022
    %
    % Each layers contains a reference from node 1 to another node with each of
    % the possible reference types.
    %
    % This test will be defining and modifying this type hierarchy and test
    % the references returned are the expected ones.

    add_references(a, [?REF(100, 1, 2), ?REF(1001, 1, 2), ?REF(1002, 1, 2),
                       ?REF(10011, 1, 2), ?REF(10012, 1, 2),
                       ?REF(10021, 1, 2), ?REF(10022, 1, 2)]),
    add_references(b, [?REF(100, 1, 3), ?REF(1001, 1, 3), ?REF(1002, 1, 3),
                       ?REF(10011, 1, 3), ?REF(10012, 1, 3),
                       ?REF(10021, 1, 3), ?REF(10022, 1, 3)]),
    add_references(c, [?REF(100, 1, 4), ?REF(1001, 1, 4), ?REF(1002, 1, 4),
                       ?REF(10011, 1, 4), ?REF(10012, 1, 4),
                       ?REF(10021, 1, 4), ?REF(10022, 1, 4)]),

    % All references types of space A
    ?assertMatch([?REF(100, 1, 2), ?REF(1001, 1, 2), ?REF(1002, 1, 2),
                  ?REF(10011, 1, 2), ?REF(10012, 1, 2),
                  ?REF(10021, 1, 2), ?REF(10022, 1, 2)],
                 lists:sort(references(a, 1, #{direction => forward}))),
    ?assertMatch([],
                 lists:sort(references(a, 1, #{direction => inverse}))),
    ?assertMatch([],
                 lists:sort(references(a, 2, #{direction => forward}))),
    ?assertMatch([?REF(100, 1, 2), ?REF(1001, 1, 2), ?REF(1002, 1, 2),
                  ?REF(10011, 1, 2), ?REF(10012, 1, 2),
                  ?REF(10021, 1, 2), ?REF(10022, 1, 2)],
                 lists:sort(references(a, 2, #{direction => inverse}))),

    % Only root reference type of space A (no reference hierarchy defined)
    ?assertMatch([?REF(100, 1, 2)],
                 lists:sort(references(a, 1,
                    #{direction => forward, type => 100}))),
    ?assertMatch([],
                 lists:sort(references(a, 1,
                    #{direction => inverse, type => 100}))),
    ?assertMatch([],
                 lists:sort(references(a, 2,
                    #{direction => forward, type => 100}))),
    ?assertMatch([?REF(100, 1, 2)],
                 lists:sort(references(a, 2,
                    #{direction => inverse, type => 100}))),

    % Root reference type and subtypes of space A (no reference hierarchy defined)
    ?assertMatch([?REF(100, 1, 2)],
                 lists:sort(references(a, 1,
                    #{direction => forward, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references(a, 1,
                    #{direction => inverse, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references(a, 2,
                    #{direction => forward, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([?REF(100, 1, 2)],
                 lists:sort(references(a, 2,
                    #{direction => inverse, type => 100,
                      include_subtypes => true}))),

    % Only Half-way reference type of space A (no reference hierarchy defined)
    ?assertMatch([?REF(1001, 1, 2)],
                 lists:sort(references(a, 1,
                    #{direction => forward, type => 1001}))),
    ?assertMatch([],
                 lists:sort(references(a, 1,
                    #{direction => inverse, type => 1001}))),
    ?assertMatch([],
                 lists:sort(references(a, 2,
                    #{direction => forward, type => 1001}))),
    ?assertMatch([?REF(1001, 1, 2)],
                 lists:sort(references(a, 2,
                    #{direction => inverse, type => 1001}))),

    % Half-way reference type and subtypes of space A (no reference hierarchy defined)
    ?assertMatch([?REF(1001, 1, 2)],
                 lists:sort(references(a, 1,
                    #{direction => forward, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references(a, 1,
                    #{direction => inverse, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references(a, 2,
                    #{direction => forward, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([?REF(1001, 1, 2)],
                 lists:sort(references(a, 2,
                    #{direction => inverse, type => 1001,
                      include_subtypes => true}))),

    % Defining part of the reference type hierarchy

    add_references(a, [?REF(?REF_HAS_SUBTYPE, 100, 1001),
                       ?REF(?REF_HAS_SUBTYPE, 1001, 10011),
                       ?REF(?REF_HAS_SUBTYPE, 1001, 10012)]),

    % Only root reference type of space A
    ?assertMatch([?REF(100, 1, 2)],
                 lists:sort(references(a, 1,
                    #{direction => forward, type => 100}))),
    ?assertMatch([],
                 lists:sort(references(a, 1,
                    #{direction => inverse, type => 100}))),
    ?assertMatch([],
                 lists:sort(references(a, 2,
                    #{direction => forward, type => 100}))),
    ?assertMatch([?REF(100, 1, 2)],
                 lists:sort(references(a, 2,
                    #{direction => inverse, type => 100}))),

    % Root reference type and subtypes of space A
    ?assertMatch([?REF(100, 1, 2), ?REF(1001, 1, 2),
                  ?REF(10011, 1, 2), ?REF(10012, 1, 2)],
                 lists:sort(references(a, 1,
                    #{direction => forward, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references(a, 1,
                    #{direction => inverse, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references(a, 2,
                    #{direction => forward, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([?REF(100, 1, 2), ?REF(1001, 1, 2),
                  ?REF(10011, 1, 2), ?REF(10012, 1, 2)],
                 lists:sort(references(a, 2,
                    #{direction => inverse, type => 100,
                      include_subtypes => true}))),

    % Only half-way reference type of space A
    ?assertMatch([?REF(1001, 1, 2)],
                 lists:sort(references(a, 1,
                    #{direction => forward, type => 1001}))),
    ?assertMatch([],
                 lists:sort(references(a, 1,
                    #{direction => inverse, type => 1001}))),
    ?assertMatch([],
                 lists:sort(references(a, 2,
                    #{direction => forward, type => 1001}))),
    ?assertMatch([?REF(1001, 1, 2)],
                 lists:sort(references(a, 2,
                    #{direction => inverse, type => 1001}))),

    % Half-way reference type and subtypes of space A
    ?assertMatch([?REF(1001, 1, 2), ?REF(10011, 1, 2), ?REF(10012, 1, 2)],
                 lists:sort(references(a, 1,
                    #{direction => forward, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references(a, 1,
                    #{direction => inverse, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references(a, 2,
                    #{direction => forward, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([?REF(1001, 1, 2), ?REF(10011, 1, 2), ?REF(10012, 1, 2)],
                 lists:sort(references(a, 2,
                    #{direction => inverse, type => 1001,
                      include_subtypes => true}))),

    % Only root reference type of space B/A
    ?assertMatch([?REF(100, 1, 2), ?REF(100, 1, 3)],
                 lists:sort(references([b, a], 1,
                    #{direction => forward, type => 100}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 1,
                    #{direction => inverse, type => 100}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 2,
                    #{direction => forward, type => 100}))),
    ?assertMatch([?REF(100, 1, 2)],
                 lists:sort(references([b, a], 2,
                    #{direction => inverse, type => 100}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 3,
                    #{direction => forward, type => 100}))),
    ?assertMatch([?REF(100, 1, 3)],
                 lists:sort(references([b, a], 3,
                    #{direction => inverse, type => 100}))),

    % Root reference type and subtypes of space B/A
    ?assertMatch([?REF(100, 1, 2), ?REF(100, 1, 3),
                  ?REF(1001, 1, 2), ?REF(1001, 1, 3),
                  ?REF(10011, 1, 2), ?REF(10011, 1, 3),
                  ?REF(10012, 1, 2), ?REF(10012, 1, 3)],
                 lists:sort(references([b, a], 1,
                    #{direction => forward, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 1,
                    #{direction => inverse, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 2,
                    #{direction => forward, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([?REF(100, 1, 2), ?REF(1001, 1, 2),
                  ?REF(10011, 1, 2), ?REF(10012, 1, 2)],
                 lists:sort(references([b, a], 2,
                    #{direction => inverse, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 3,
                    #{direction => forward, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([?REF(100, 1, 3), ?REF(1001, 1, 3),
                  ?REF(10011, 1, 3), ?REF(10012, 1, 3)],
                 lists:sort(references([b, a], 3,
                    #{direction => inverse, type => 100,
                      include_subtypes => true}))),

    % Only half-way reference type of space A
    ?assertMatch([?REF(1001, 1, 2), ?REF(1001, 1, 3)],
                 lists:sort(references([b, a], 1,
                    #{direction => forward, type => 1001}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 1,
                    #{direction => inverse, type => 1001}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 2,
                    #{direction => forward, type => 1001}))),
    ?assertMatch([?REF(1001, 1, 2)],
                 lists:sort(references([b, a], 2,
                    #{direction => inverse, type => 1001}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 3,
                    #{direction => forward, type => 1001}))),
    ?assertMatch([?REF(1001, 1, 3)],
                 lists:sort(references([b, a], 3,
                    #{direction => inverse, type => 1001}))),

    % Half-way reference type and subtypes of space A
    ?assertMatch([?REF(1001, 1, 2), ?REF(1001, 1, 3),
                  ?REF(10011, 1, 2), ?REF(10011, 1, 3),
                  ?REF(10012, 1, 2), ?REF(10012, 1, 3)],
                 lists:sort(references([b, a], 1,
                    #{direction => forward, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 1,
                    #{direction => inverse, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 2,
                    #{direction => forward, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([?REF(1001, 1, 2), ?REF(10011, 1, 2), ?REF(10012, 1, 2)],
                 lists:sort(references([b, a], 2,
                    #{direction => inverse, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([],
                 lists:sort(references([b, a], 3,
                    #{direction => forward, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([?REF(1001, 1, 3), ?REF(10011, 1, 3), ?REF(10012, 1, 3)],
                 lists:sort(references([b, a], 3,
                    #{direction => inverse, type => 1001,
                      include_subtypes => true}))),

    % Defining more of the reference type hierarchy in the sub-space B/A,
    % deleting some defined in the super-space.

    add_references([b, a], [?REF(?REF_HAS_SUBTYPE, 100, 1002),
                            ?REF(?REF_HAS_SUBTYPE, 1002, 10021)]),
    del_references([b, a], [?REF(?REF_HAS_SUBTYPE, 1001, 10012)]),

    ?assertMatch([?REF(100, 1, 2), ?REF(100, 1, 3),
                  ?REF(1001, 1, 2), ?REF(1001, 1, 3),
                  ?REF(1002, 1, 2), ?REF(1002, 1, 3),
                  ?REF(10011, 1, 2), ?REF(10011, 1, 3),
                  ?REF(10021, 1, 2), ?REF(10021, 1, 3)],
                 lists:sort(references([b, a], 1,
                    #{direction => forward, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([?REF(1001, 1, 2), ?REF(1001, 1, 3),
                  ?REF(10011, 1, 2), ?REF(10011, 1, 3)],
                 lists:sort(references([b, a], 1,
                    #{direction => forward, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([?REF(1002, 1, 2), ?REF(1002, 1, 3),
                  ?REF(10021, 1, 2), ?REF(10021, 1, 3)],
                 lists:sort(references([b, a], 1,
                    #{direction => forward, type => 1002,
                      include_subtypes => true}))),


    % Defining more of the reference type hierarchy in the sub-space C/B/A,
    % deleting some defined in the super-spaces.

    add_references([c, b, a], [?REF(?REF_HAS_SUBTYPE, 1002, 10022),
                               ?REF(?REF_HAS_SUBTYPE, 1001, 10012)]),
    del_references([c, b, a], [?REF(?REF_HAS_SUBTYPE, 1001, 10011),
                               ?REF(?REF_HAS_SUBTYPE, 1002, 10021)]),

    ?assertMatch([?REF(100, 1, 2), ?REF(100, 1, 3), ?REF(100, 1, 4),
                  ?REF(1001, 1, 2), ?REF(1001, 1, 3), ?REF(1001, 1, 4),
                  ?REF(1002, 1, 2), ?REF(1002, 1, 3), ?REF(1002, 1, 4),
                  ?REF(10012, 1, 2), ?REF(10012, 1, 3), ?REF(10012, 1, 4),
                  ?REF(10022, 1, 2), ?REF(10022, 1, 3), ?REF(10022, 1, 4)],
                 lists:sort(references([c, b, a], 1,
                    #{direction => forward, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([?REF(1001, 1, 2), ?REF(1001, 1, 3), ?REF(1001, 1, 4),
                  ?REF(10012, 1, 2), ?REF(10012, 1, 3), ?REF(10012, 1, 4)],
                 lists:sort(references([c, b, a], 1,
                    #{direction => forward, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([?REF(1002, 1, 2), ?REF(1002, 1, 3), ?REF(1002, 1, 4),
                  ?REF(10022, 1, 2), ?REF(10022, 1, 3), ?REF(10022, 1, 4)],
                 lists:sort(references([c, b, a], 1,
                    #{direction => forward, type => 1002,
                      include_subtypes => true}))),

    % Adding back all the removed reference types

    add_references([c, b, a], [?REF(?REF_HAS_SUBTYPE, 1001, 10011),
                               ?REF(?REF_HAS_SUBTYPE, 1002, 10021)]),

    ?assertMatch([?REF(100, 1, 2), ?REF(100, 1, 3), ?REF(100, 1, 4),
                  ?REF(1001, 1, 2), ?REF(1001, 1, 3), ?REF(1001, 1, 4),
                  ?REF(1002, 1, 2), ?REF(1002, 1, 3), ?REF(1002, 1, 4),
                  ?REF(10011, 1, 2), ?REF(10011, 1, 3), ?REF(10011, 1, 4),
                  ?REF(10012, 1, 2), ?REF(10012, 1, 3), ?REF(10012, 1, 4),
                  ?REF(10021, 1, 2), ?REF(10021, 1, 3), ?REF(10021, 1, 4),
                  ?REF(10022, 1, 2), ?REF(10022, 1, 3), ?REF(10022, 1, 4)],
                 lists:sort(references([c, b, a], 1,
                    #{direction => forward, type => 100,
                      include_subtypes => true}))),
    ?assertMatch([?REF(1001, 1, 2), ?REF(1001, 1, 3), ?REF(1001, 1, 4),
                  ?REF(10011, 1, 2), ?REF(10011, 1, 3), ?REF(10011, 1, 4),
                  ?REF(10012, 1, 2), ?REF(10012, 1, 3), ?REF(10012, 1, 4)],
                 lists:sort(references([c, b, a], 1,
                    #{direction => forward, type => 1001,
                      include_subtypes => true}))),
    ?assertMatch([?REF(1002, 1, 2), ?REF(1002, 1, 3), ?REF(1002, 1, 4),
                  ?REF(10021, 1, 2), ?REF(10021, 1, 3), ?REF(10021, 1, 4),
                  ?REF(10022, 1, 2), ?REF(10022, 1, 3), ?REF(10022, 1, 4)],
                 lists:sort(references([c, b, a], 1,
                    #{direction => forward, type => 1002,
                      include_subtypes => true}))),


    terminate(c),
    terminate(b),
    terminate(a),
    ok.